/*
 * @Author: 13177709051 3506463075@qq.com
 * @Date: 2024-11-14 13:45:18
 * @LastEditors: 13177709051 3506463075@qq.com
 * @LastEditTime: 2024-11-21 21:30:52
 * @FilePath: /linux33/Log.hpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
/*
 * @Author: 13177709051 3506463075@qq.com
 * @Date: 2024-11-05 22:11:30
 * @LastEditors: 13177709051 3506463075@qq.com
 * @LastEditTime: 2024-11-05 22:12:52
 * @FilePath: /linux31/udp_echo_server/Log.hpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */


#pragma once

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"

namespace log_ns
{

    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string LevelToString(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
        case INFO:
            return "INFO";
        case WARNING:
            return "WARNING";
        case ERROR:
            return "ERROR";
        case FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetCurrTime()
    {
        time_t now = time(nullptr);
        struct tm *curr_time = localtime(&now);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
                 curr_time->tm_year + 1900,
                 curr_time->tm_mon + 1,
                 curr_time->tm_mday,
                 curr_time->tm_hour,
                 curr_time->tm_min,
                 curr_time->tm_sec);
        return buffer;
    }

    class logmessage
    {
    public:
        std::string _level;
        pid_t _id;
        std::string _filename;
        int _filenumber;
        std::string _curr_time;
        std::string _message_info;
    };

#define SCREEN_TYPE 1
#define FILE_TYPE 2

    const std::string glogfile = "./log.txt";
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    // log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , );
    class Log
    {
    public:
        Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
        {
        }
        void Enable(int type)
        {
            _type = type;
        }
        void FlushLogToScreen(const logmessage &lg)
        {
            printf("[%s][%d][%s][%d][%s] %s",
                   lg._level.c_str(),
                   lg._id,
                   lg._filename.c_str(),
                   lg._filenumber,
                   lg._curr_time.c_str(),
                   lg._message_info.c_str());
        }
        void FlushLogToFile(const logmessage &lg)
        {
            std::ofstream out(_logfile, std::ios::app);
            if (!out.is_open())
                return;
            char logtxt[2048];
            snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
                     lg._level.c_str(),
                     lg._id,
                     lg._filename.c_str(),
                     lg._filenumber,
                     lg._curr_time.c_str(),
                     lg._message_info.c_str());
            out.write(logtxt, strlen(logtxt));
            out.close();
        }
        void FlushLog(const logmessage &lg)
        {
            // 加过滤逻辑 --- TODO

            LockGuard lockguard(&glock);
            switch (_type)
            {
            case SCREEN_TYPE:
                FlushLogToScreen(lg);
                break;
            case FILE_TYPE:
                FlushLogToFile(lg);
                break;
            }
        }
        void logMessage(std::string filename, int filenumber, int level, const char *format, ...)
        {
            logmessage lg;

            lg._level = LevelToString(level);
            lg._id = getpid();
            lg._filename = filename;
            lg._filenumber = filenumber;
            lg._curr_time = GetCurrTime();

            // va_list 是一个结构体类型，专门用于存储指向可变参数列表的指针。当你使用可变参数时，首先要通过 va_start 宏初始化一个 va_list 变量，然后用它来访问每个可变参数。
            va_list ap;
            va_start(ap, format);
            char log_info[1024];
            vsnprintf(log_info, sizeof(log_info), format, ap);
            va_end(ap);
            lg._message_info = log_info;

            // 打印出来日志
            FlushLog(lg);
        }
        ~Log()
        {
        }

    private:
        int _type;
        std::string _logfile;
    };

    Log lg;

#define LOG(Level, Format, ...)                                        \
    do                                                                 \
    {                                                                  \
        lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
    } while (0)
#define EnableScreen()          \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)
#define EnableFILE()          \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)
};


// 这是日志级别的枚举，定义了五个日志级别：DEBUG、INFO、WARNING、ERROR 和 FATAL，数字1到5代表不同的优先级。

// 1. DEBUG (值: 1)
// 含义: DEBUG 级别的日志用于调试目的，主要记录程序运行中的详细信息，帮助开发人员理解程序的内部状态。这些信息通常对开发人员在开发、调试、分析问题时非常有用。
// 应用场景:
// 记录函数调用的输入输出参数。
// 输出变量的值、内存地址或其他详细的系统状态信息。
// 打印细节，像是某个功能执行的具体步骤、算法的中间结果等。
// 优先级: 最低的日志优先级，通常只在开发环境中启用。

// 2. INFO (值: 2)
// 含义: INFO 级别的日志用于记录常规信息，表示系统按预期运行的正常操作。这些日志通常用于展示系统的状态或者某些操作的成功执行，提供程序执行过程中有用的背景信息。
// 应用场景:
// 系统启动或停止。
// 配置的加载。
// 用户执行操作的成功消息。
// 优先级: 相对较低，但仍然是一个有用的日志级别。适用于生产环境中记录系统的常规行为和关键事件。

// 3. WARNING (值: 3)
// 含义: WARNING 级别的日志用于记录潜在的问题或不正常的情况，这些情况可能不会导致程序崩溃或立即失败，但可能会影响程序的执行或系统的稳定性。警告通常表明某些操作可能需要关注或修改，但并不一定立即需要处理。
// 应用场景:
// 配置文件中的可疑设置或过时的配置项。
// 资源的使用接近极限，比如内存占用接近最大值。
// 网络延迟、连接问题等。
// 优先级: 高于 INFO 级别，意味着它是需要关注的，但不至于影响程序的正常运行。

// 4. ERROR (值: 4)
// 含义: ERROR 级别的日志用于记录系统中发生的错误，这些错误通常会导致某些功能或操作失败，但不会完全中断系统的运行。错误通常需要被开发人员注意和修复，或者需要采取措施来避免进一步的问题。
// 应用场景:
// 数据库连接失败。
// 文件读写错误。
// 网络请求失败。
// 输入参数错误，导致某个功能不能正常执行。
// 优先级: 高于 WARNING 级别，意味着这类问题更严重，但仍然不是致命的。通常会影响用户体验，需要开发人员快速处理。

// 5. FATAL (值: 5)
// 含义: FATAL 级别的日志用于记录严重错误，这些错误通常会导致程序崩溃或系统完全无法继续运行。FATAL 级别的日志代表最严重的错误，需要立即处理。这些错误通常是程序中的致命缺陷，可能需要紧急修复或采取特殊措施来恢复系统的正常运行。
// 应用场景:
// 程序崩溃或内存泄漏导致系统无法继续运行。
// 系统无法启动或重要组件丢失。
// 某些关键操作失败，无法继续执行后续步骤。
// 优先级: 最高的日志优先级。需要立即采取措施，通常需要系统管理员或开发人员介入。



// 3. LevelToString 函数
// 该函数用于将日志级别数字转换为对应的字符串：

// 4. GetCurrTime 函数
// 获取当前系统时间并格式化为字符串。使用 strftime 格式化时间，返回一个格式化后的时间字符串：

// logmessage 类用于封装日志消息的结构体，包含：
// _level：日志级别
// _id：进程ID
// _filename：源代码文件名
// _filenumber：行号
// _curr_time：当前时间
// _message_info：实际的日志消息内容


// Log 类Log 类是日志管理的核心类，包含了日志的输出控制和处理方法。它的主要功能是：

// 构造函数：接受一个日志文件路径并初始化日志类型为屏幕输出（SCREEN_TYPE）。
// Enable 方法：设置日志输出类型，支持屏幕输出或文件输出。
// FlushLogToScreen：将日志信息输出到屏幕。
// FlushLogToFile：将日志信息写入文件。默认文件路径为 ./log.txt。
// FlushLog：根据日志类型决定将日志输出到屏幕还是文件。使用 LockGuard 实现线程安全。
// logMessage 方法：日志记录的核心方法，接受文件名、行号、日志级别和日志内容格式化字符串。通过 va_list 支持可变参数。
// 析构函数：析构时没有特别的清理操作。



// 2. #define LOG(Level, Format, ...)
// LOG 是一个宏，用于简化日志记录的调用。这个宏接受以下参数：
// Level: 日志的级别（如 INFO, DEBUG, ERROR 等）。
// Format: 格式化字符串，类似于 printf 中使用的格式说明符（例如 %s, %d）。
// ...: 可变参数，可以传递给 logMessage 方法，具体取决于格式字符串和实际参数。

// do { ... } while (0)：这种结构常用于宏定义中，目的是将宏的多条语句包裹在一个代码块中，并确保宏使用时不受周围代码的影响。它保证了宏的调用语句总是作为一个完整的语句来执行。
// __FILE__ 和 __LINE__：这两个预定义宏分别提供当前源文件的文件名和当前行号，用来记录日志发生的位置。
// lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__)：这是宏的核心部分，调用 Log 类的 logMessage 方法。它将当前文件名、行号、日志级别、格式字符串和变长参数传递给 logMessage。
// __FILE__ 和 __LINE__ 提供日志的上下文信息。
// Level 指定日志的级别（如 INFO、ERROR 等）。
// Format 是日志的格式字符串。
// ##__VA_ARGS__ 用来传递实际的参数到 logMessage 中，## 语法在某些编译器中可以去掉多余的逗号（比如如果没有传递变长参数时）。


// 3. #define EnableScreen()
// 该宏用于启用屏幕日志输出。宏的定义如下：
// lg.Enable(SCREEN_TYPE)：调用 Log 类的 Enable 方法，传递一个常量 SCREEN_TYPE，该常量可能表示日志的输出目标是屏幕。具体来说，Enable 方法可能是配置日志输出的目标或启用某种日志记录模式。
// do { ... } while (0)：同样，使用这种方式包裹宏，确保宏调用的语法不会受到其他代码块的影响

// #define EnableFILE()
// lg.Enable(FILE_TYPE)：调用 Log 类的 Enable 方法，传递一个常量 FILE_TYPE，表示日志的输出目标是文件。FILE_TYPE 很可能是一个标志常量，表示日志应该写入到文件而不是其他地方（例如屏幕）。
// do { ... } while (0)：同样使用这个结构，以确保宏调用时不受外部代码的影响。
